

/**
 *
 * Ori Weisler  ID 060512878
 * Rony Lahav   ID 043489889 
 * Date: Jun 24, 2009
 */

import java.io.*;


public class Lexer
{
	private final static String STR_PROG_FILENAME = "prog.txt";
    private final static String STR_IN_FILENAME = "input.txt";
    private final static String STR_JVM_FILENAME = "inputJVM.txt";
    private final static int main=1000, times=1, div=3, struct=1003, int_=1002, becomes=33, minus=7, plus=6, cin=1005, cout=1007,
	out=1008, in_=1006, dot=18, comma=19, rparen=22, lparen=29, rbrak=23, lbrak=30,
	number=34, ident=37, semicolon=38, const_=57, lblock=1001, rblock=1004, error=666,
	enter=25, space=25, EOF_=64;

	public char ch;
	public int sym, val;
	public static String var;
	public static Table obj;
	public BufferedReader in;
    private int index=0;
    private static String input;
    private static BufferedWriter output;


    /**
     * Used for printing - convert int symbol to sign for printing
     * @param symbol
     * @return
     */
     public String toString(int symbol){
        switch(symbol){
            case main:
                return "main()";
             case times:
                return "*";
            case plus:
                return "+";
            case minus:
                return "-";
            case becomes:
                return "=";
            case out:
                return "<<";
            case in_:
                return ">>";
            case semicolon:
                return ";";
            case comma:
                return ",";
            case dot:
                return ".";
            case lparen:
                return "(";
            case rparen:
                return ")";
            case lbrak:
                return "[";
            case rbrak:
                return "]";
            case lblock:
                return "{";
            case rblock:
                return "}";
            case div:
                return "/";
            case int_:
                return "int";
            default:
                return "";
        }
    }

    /**
     * return char according to index. We are using a buffer for each row, but
     * for oomfortability, working with one char only each time
     * @return
     */
    private char getNextChar() {
        char c;
        if( index < input.length() ){
            c = input.charAt(index);
            index++;
            return c;
        }
        else{
            return (char)-1;
        }

    }

    //return one token
	public int getSym(){
		String buffer;
		while (true){
			try{
				 if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')){
					buffer = new String(ch + "");
					do{
						ch = this.getNextChar();//char) in.read();
						if((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))
							buffer = buffer + ch;
					} while ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'));
                    if (buffer.equals("main"))
					{
						return main;
					}
					if (buffer.equals("struct"))
					{
						return struct;
					}
					if (buffer.equals("cin"))
					{
						return cin;
					}
					if (buffer.equals("cout"))
					{
						return cout;
					}
					if (buffer.equals("int"))
					{
						return int_;
					}
					var = new String(buffer);  //save the last ident
					return ident;
				}
				else{
					if (ch >= '0' && ch <= '9')
					{
						buffer = new String(ch + "");
						do
						{
							ch = this.getNextChar();//char) in.read();
							if (ch >= '0' && ch <= '9')
								buffer = buffer + ch;
						} while (ch >= '0' && ch <= '9');
						if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
						{
							buffer = buffer + ch;
							do
							{
								ch = this.getNextChar();//char) in.read();
								if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))
									buffer = buffer + ch;
							} while ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'));
							return error;
						}
                        val = Integer.parseInt(buffer);  //save the last number
						return number;
					}
					else
					{
						if((byte)ch == -1)
						{
							return EOF_;
						}
                        int returnVal;
                        boolean ioflag = false;
                        switch (ch)
						{
							case '*':
                                returnVal = times;
                                break;
                            case '+':
                                returnVal = plus;
                                break;
                            case '-':
                                returnVal = minus;
                                break;
                            case '=':
                                returnVal = becomes;
                                break;
                            case '<':
                                if (ch == '<')
								{
                                    ioflag = true;
                                    returnVal = out;
                                }
                                else
                                {
                                    returnVal = error;
                                }
                                break;
                            case '>':
                                if (ch == '>')
                                {
                                    ioflag = true;
                                    returnVal = in_;
                                }
                                else
                                {
                                    returnVal = error;
                                }
                                break;
                            case ';':
                                returnVal = semicolon;
                                break;
                            case ',':
                                returnVal = comma;
                                break;
                            case '.':
                                returnVal = dot;
                                break;
                            case '(':
                                returnVal = lparen;
                                break;
                            case ')':
                                returnVal = rparen;
                                break;
                            case '[':
                                returnVal = lbrak;
                                break;
                            case ']':
                                returnVal = rbrak;
                                break;
                            case '{':
                                returnVal = lblock;
                                break;
                            case '}':
                                returnVal = rblock;
                                break;
                            case '/':
                                returnVal = div;
                                break;
                            case '\t':
							case ' ':
							case '\n':
							case '\r':
                                ch = this.getNextChar();
                                continue;
							default:
                                returnVal = error;
                                break;
                        }
                        ch = this.getNextChar();
                        if ( ioflag == true ){
                            if( ch == '<')
                                returnVal = out;
                            if( ch == '>' )
                                returnVal = in_;
                            ch = this.getNextChar();
                        }
                        return returnVal;
                    }
                 }
            }
			catch (Exception e)
			{
                if( ch != -1)
                    return ch;
                else{
                    ch = (char) -1;
                    return -1;
                }
                
			}
        }
    }

	/**reads the entire file into the buffer and on the way creates the symbol table**/
    public static void readInputFileAndInitSymTable() throws IOException{
    	input = "";
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader( STR_PROG_FILENAME ));
            String line = in.readLine();   //reads "main(){"
            boolean creating = true;
            do{ //reads the initialization lines and creates the symbol table
	            line = in.readLine();
	            if (line.contains("struct")){	//might be defined in more than 1 line
	            	String endStruct;
	            	do{
	            		endStruct = in.readLine();
	            		line += " " + endStruct;
	            	} while (!endStruct.contains("}"));
	            }
            creating = Compiler.symTable.createNewSymTable(line);
            }while ( creating );
            do{
                input += line + " ";
                line = in.readLine();
            }while (line != null);  //EOF
            
        } catch ( Exception e ) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        finally{
            in.close();
        }
	}
    
    /**contains the linckage between the variable's adress/value(<-for const) and the register containing the value**/
    private class Item{
    	public String mode;
    	public int a,r;
    	
    }
    
    /**main parser function - converts the program from C-- to JVM**/
    public void statSequance() throws IOException{
        try {
        	boolean exit=false;
            output = new BufferedWriter(new FileWriter( STR_JVM_FILENAME));
            ch = this.getNextChar();
            Item x=new Item();
            Item y=new Item();
            sym = getSym();
            while(!exit){
	            if (sym==this.ident){
	            	obj = SymTable.getInstance().getTableByName(var);
	            	sym=getSym();
	            	x = makeItem(x,obj);
	            	if (sym == this.dot || sym == this.lbrak)	//for arrays or structs
	            		x = selector(x, obj);
	            	if (sym==this.becomes){	//for "="
	            		sym=getSym();
	            		y = expression(y);
	            		y = store(x,y);
	            	}
	            	else System.out.println("error");
	            }
	            else if (sym == this.cin){
	            	sym = getSym();
	            	if (sym == this.in_)
	            		ioIn();
	            }
	            else if (sym == this.cout){
	            	sym = getSym();
	            	if (sym==this.out)
	            		ioOut();
	            }
	            if (sym==this.semicolon){	//end of program or end of file
	            	sym=getSym();
	            	if (sym==this.rblock || sym == this.EOF_)
	            		exit=true;
	            }
	            else
	            	System.out.println("errof");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally{
            output.close();
        }
    }
    
    /**deals with reading a value from the variables file into a local variable**/
    private void ioIn(){
    	Item x = new Item();
    	sym=getSym();
    	obj = SymTable.getInstance().getTableByName(var);
    	x = makeItem(x,obj);
    	sym=getSym();
    	if (sym == this.dot || sym == this.lbrak)
    		x = selector(x, obj);
    	x.r = getReg();
    	put("RD", 0, 0, x.r);			//	read from input
		put("STW", x.r, 0,x.a);			//	store in the memory
		decReg();
    }
    
    /** deals with printing a value stored in the variable's register**/
    private void ioOut(){
    	Item x = new Item();
    	sym=getSym();
    	x = expression(x);
    	x = load(x);
    	this.put("WRD", 0, 0, x.r);		//	write to JVM file
    	decReg();
    }
    
    /**deals with creating an Item for an array or struct**/
    private Item selector(Item x, Table obj){
    	if (sym == this.lbrak){	//array
    		sym=getSym();
    		x.a += val*4;	//the shift in the array's adress for the var's location (4 bytes for each var)
    		sym=getSym();	//"]"
    		sym=getSym();	//actual next sym
    	}
    	else{	//struct
    		sym=getSym();
    		obj = SymTable.getInstance().getTableByName(obj.varName+"#"+this.var);
    		x=makeItem(x, obj);
    		sym=getSym();	//actual next sym
    	}
    	return x;
    }
    
    /**deals with a mathematical expression**/
    private Item expression (Item x){
    	Item y=new Item();
    	int op;
    	if (sym==this.plus){
    		sym=getSym();
    		x = term(x);
    	}
    	else if (sym==this.minus){
    		sym=getSym();
    		x = term(x);
    		x = op1(this.minus,x);
    	}
    	else
    		x = term(x);
    	while(sym==this.plus || sym==this.minus){
    		op=sym;
    		sym=getSym();
    		y = term(y);
    		x = op2(op,x,y);
    	}
    	return x;
    }
    
    /**stores a value to memory**/
    private Item store(Item x, Item y){
    	if (!y.mode.equals("register"))
    		y=load(y);
    	if (x.mode.equals("var"))
    		put("STW",y.r,x.r,x.a);
    	else
    		System.out.println("error");
    	return y;
    }
    
    /**deals with a mathematical term**/
    private Item term(Item x){
    	Item y=new Item();
    	int op;
    	x = factor(x);
    	while (sym==this.times || sym==this.div){
    		op=sym;
    		sym=getSym();
    		y = factor(y);
    		x = op2(op,x,y);
    	}
    	return x;
    }
    
    /**deals with an unary operation of SUB for constant folding**/
    private Item op1(int op, Item x){
    	if (op==this.minus){
    		if (x.mode.equals("const"))
    			Compiler.memory[x.a]=-Compiler.memory[x.a];
    	}
    	else if (x.mode.equals("var"))
    		x=load(x);
    	put("SUB",x.r,0,x.r);
    	return x;
    }
     
    /**dealse with a binary mathematical operation with constant folding**/
    private Item op2(int op, Item x, Item y){
    	if (x.mode.equals("const") && y.mode.equals("const")){
    		if (op==this.plus)
    			Compiler.memory[x.a] += Compiler.memory[y.a]; //INC(x.a,y.a)
    		else if (op==this.minus)
    			Compiler.memory[x.a] -= Compiler.memory[y.a]; //DEC(x.a,y.a)
    		else if(op==this.times)
    			Compiler.memory[x.a] *= Compiler.memory[y.a];
    		else if (op==this.div)
    			Compiler.memory[x.a] /= Compiler.memory[y.a];
    		else
    			System.out.println("error");
    	}
    	else if (op==this.plus)
    		putOp("ADD",x,y);
    	else if(op==this.minus)
    		putOp("SUB",x,y);
    	else if(op==this.times)
    		putOp("MUL",x,y);
    	else if(op==this.div)
    		putOp("DIV",x,y);
    	else if(op==this.minus)
    		putOp("SUB",x,y);
    	else
    		System.out.println("error");
    	return x;
    }
    
    /**writes an operation to the JVM file**/
    private void putOp(String cd,Item x, Item y){
    	int r;
    	if (!x.mode.equals("register"))
    		x=load(x);
    	if (x.r==0){
    		r=getReg();
    		r=0;
    	}
    	else
    		r=x.r;
    	if (y.mode.equals("const"))
    		put(cd+"I",r,x.r,y.a);
    	else{
    		if(!y.mode.equals("register"))   	
    			y = load(y);
    		put(cd,x.r,r,y.r);
    	}
    }
    
    /**loads a var's data into its Item for the symbol table**/
    private Item load(Item x){
    	int r = 0;
    	if (x.mode.equals("var")){
    		r = getReg();
    		put("LDW",r,x.r,x.a);
    		x.r=r;
    	}
    	else if(x.mode.equals("const"))
    		if (x.a==0)
    			x.r=0;
    		else{
    			x.r = getReg();
    			put("ADDI",x.r,0,x.a);
    		}
    	x.mode="register";
    	return x;
    }
    
    /**asigns next available register**/
    private int getReg(){
    	++Compiler.nextReg;
    	return (Compiler.nextReg-1);
    }
    
    private void decReg(){
    	--Compiler.nextReg;
    }
    
    /**deals with parenthesis**/
    private Item factor(Item x){
    	if (sym==this.ident){
    		obj = SymTable.getInstance().getTableByName(var);
    		sym=getSym();
    		x = makeItem(x, obj);
    		if (sym == this.dot || sym == this.lbrak)
        		x = selector(x, obj);
    	}
    	else if (sym==this.number){
    		x = makeConstItem(x, val);
    		sym=getSym();
    	}
    	else if(sym==this.lparen){
    		sym=getSym();
    		x = expression(x);
    		if (sym==this.rparen)
    			sym=getSym();
    		else System.out.println("error");
    	}
    	else
    		System.out.println("error");
    	return x;
    }
    
    /**creats an Item for a const**/
    private Item makeConstItem(Item x, int val){
    	x.mode="const";
    	x.a=val;
    	x.r=0;
    	return x;
    }
    
    /**creates an Item for a var**/
    private Item makeItem(Item x, Table y){
    	x.mode = y.varType;
    	if (y.varType.equals("const"))
    		x.a = Compiler.memory[y.varMem];
    	else
    		x.a = y.varMem;//SymTable.getInstance().getVarValueByName(y.varName);
    	x.r=0;
    	return x;
    }
    
    /**write the action to the JVM file**/
    private void put(String cd, int dest, int r1, int r2){
    	try {
			output.write(cd + " " + Integer.toString(dest) + "," + Integer.toString(r1) + "," + Integer.toString(r2) );
			output.newLine();
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
    
}




